﻿//-----------------------------------------------------------------------------------------------------------
// Wbfs Engine (WBFSe) Project by Omega Frost 
// http://wbfse.codeplex.com/
//
// WBFSe is Licensed under the terms of the 
// Microsoft Reciprocal License (Ms-RL)
//-----------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WBFSe.IO;
using System.IO;
using WBFSe.FormatApi;
using WBFSe.IO.Drives;
using WBFSe.IO.Streams;
using WBFSe.Attributes;

namespace WBFSe.Formats
{
    /// <summary>
    /// Wbfs partition reader
    /// </summary>
    class PWbfsReader : WbfsReader
    {
        // Variables

        WbfsDrive drive;
        ushort[] sectors;
        byte[] header;

        // Methods

        /// <summary>
        /// Finds the container files in the array
        /// </summary>
        /// <param name="directory">The source folder</param>
        /// <param name="files">The files inside the source folder</param>
        /// <param name="filename">The desired filename</param>
        /// <exception cref="System.FileNotFoundException">If filename not found</exception>
        /// <exception cref="System.Exception">On operation failure</exception>
        public override void GetFiles(string directory, string[] files, string filename)
        {
            drive = WbfsDevices.GetDrive(directory) as WbfsDrive;

            this.path = directory;
            this.filenames = new List<string>();
            this.filenames.Add(filename);
        }

        /// <summary>
        /// Parses the container
        /// </summary>
        /// <exception cref="System.Exception">On operation failure</exception>
        public override void ParseContainer()
        {
            this.version = 1;

            if (!this.drive.QueryDiscDesc(this.filenames[0], out this.header, out this.sectors))
                throw new FileNotFoundException("File not found", this.filenames[0]);

            // Only support is for Wii game
            this.gameType = Attributes.GameType.Wii;

            this.gameCode = ReadString(this.header, WbfsConstantsV1.WiiCodePos, WbfsConstantsV1.WiiCodeLen);
            this.gameName = ReadString(this.header, WbfsConstantsV1.WiiNamePos, WbfsConstantsV1.WiiNameLen);
            this.gameRegion = GetRegionFromCode(this.gameCode);

            Boolean[] usage = new Boolean[this.sectors.Length];
            for (int i = 0; i < usage.Length; i++)
            {
                if (this.sectors[i] != 0)
                    usage[i] = true;
            }

            this.sectorSize = this.drive.WbfsSectorSize;
            this.sectorUsage = new UsageTable(usage);
            this.fileSize = this.sectorSize *
                this.sectorUsage.UsedSectors;
        }

        /// <summary>
        /// Verifies if there's a game inside the container,
        /// should be used only by multi purpose containers
        /// such as RAR, ZIP, ISO. Avoid throwing exceptions
        /// inside this method, it can lead to poor engine
        /// performance on reading multiple files
        /// </summary>
        /// <returns>True if there's a game in the container</returns>
        public override bool IsGame()
        {
            return true;
        }

        /// <summary>
        /// Changes the attributes of the game in the container's header
        /// </summary>
        /// <param name="name">The new name of the game</param>
        /// <param name="code">The new code of the game</param>
        /// <param name="region">The new region of the game</param>
        public override void ChangeHeader(string name, string code, Attributes.Region region)
        {
            if (code.Length > WbfsConstantsV1.WiiCodeLen)
                code = code.Substring(0, WbfsConstantsV1.WiiCodeLen);

            if (name.Length > WbfsConstantsV1.WiiNameLen)
                name = name.Substring(0, WbfsConstantsV1.WiiNameLen);

            Array.Clear(this.header, WbfsConstantsV1.WiiCodePos, WbfsConstantsV1.WiiCodeLen);
            Array.Clear(this.header, WbfsConstantsV1.WiiNamePos, WbfsConstantsV1.WiiNameLen);

            Encoding.Default.GetBytes(code).CopyTo(this.header, WbfsConstantsV1.WiiCodePos);
            Encoding.Default.GetBytes(name).CopyTo(this.header, WbfsConstantsV1.WiiNamePos);

            if (!this.drive.SetDiscDesc(this.filenames[0], this.header, null))
                throw new FileNotFoundException("File not found", this.filenames[0]);
        }

        /// <summary>
        /// Creates a Stream from the container files
        /// </summary>
        /// <param name="access">Desired acess</param>
        /// <param name="share">Desired sharing</param>
        /// <returns>Stream</returns>
        /// <exception cref="System.Exception">On operation failure</exception>
        protected override IWbfsStream GetStream(FileAccess access, FileShare share)
        {
            return new WbfsDriveDiscStream(this.drive, this.sectors, drive.DiscLength);
        }

        /// <summary>
        /// Cancels pending operations and closes streams
        /// </summary>
        protected override void Closing()
        {
            // Do nothing...
        }

        /// <summary>
        /// Disposes the container
        /// </summary>
        public override void Dispose()
        {
            // Do nothing...
        }

        /// <summary>
        /// Extracts the region information from a game code
        /// </summary>
        /// <param name="code">Game code</param>
        /// <returns>Region</returns>
        private Region GetRegionFromCode(string code)
        {
            return GetRegionFromCode((byte)(Char.ToLowerInvariant(code[3])));
        }

        /// <summary>
        /// Converts a region byte to a region code
        /// </summary>
        /// <param name="regionbyte">Region byte</param>
        /// <returns>Region</returns>
        private Region GetRegionFromCode(byte regionbyte)
        {
            //Convert to uppercase
            if (regionbyte > 90)
                regionbyte -= 32;

            switch (regionbyte)
            {
                case 0x41: return Region.RegionFree;
                case 0x43: return Region.Custom;
                case 0x44: return Region.PALGerman;
                case 0x45: return Region.NTSCU;
                case 0x46: return Region.PALFrench;
                case 0x4A: return Region.NTSCJ;
                case 0x4B: return Region.KOR;
                case 0x4C: return Region.PALWorld;
                case 0x50: return Region.PAL;
                case 0x51: return Region.KORJapanese;
                case 0x54: return Region.KOREnglish;
                case 0x58: return Region.PALOther;
                default: return Region.Unknown;
            }
        }

        /// <summary>
        /// Reads a string from a buffer
        /// </summary>
        /// <param name="buffer">Input buffer</param>
        /// <param name="offset">Offset in buffer</param>
        /// <param name="max">Maximum number of characters, set -1 to disable</param>
        /// <returns>String</returns>
        private string ReadString(byte[] buffer, int offset, int max)
        {
            StringBuilder builder;
            if (max <= 0) builder = new StringBuilder();
            else builder = new StringBuilder(max);

            while ((max > 0) || (max < 0))
            {
                if (buffer[offset] == 0)
                    break;

                builder.Append((char)buffer[offset]);

                offset++;
                max--;
            }

            return builder.ToString();
        }
    }
}
